import { Injectable, ConflictException, UnauthorizedException, BadRequestException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma/prisma.service';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { AuthResponseDto, UserResponseDto } from './dto/auth-response.dto';
import { formatBeijingISO } from '../common/date.util';
import * as bcrypt from 'bcryptjs';
import { Gender } from '@prisma/client';

@Injectable()
export class AuthService {
  constructor(
    private prisma: PrismaService,
    private jwtService: JwtService,
  ) {}

  // User registration
  async register(registerDto: RegisterDto): Promise<AuthResponseDto> {
    const { email, phone, password, gender, nickname } = registerDto;

    // Validate that at least email or phone is provided
    if (!email && !phone) {
      throw new BadRequestException('At least email or phone number must be provided');
    }

    // Check if email already exists
    if (email) {
      const existingUserByEmail = await this.prisma.user.findUnique({
        where: { email },
      });
      if (existingUserByEmail) {
        throw new ConflictException('Email already registered');
      }
    }

    // Check if phone number already exists
    if (phone) {
      const existingUserByPhone = await this.prisma.user.findUnique({
        where: { phone },
      });
      if (existingUserByPhone) {
        throw new ConflictException('Phone number already registered');
      }
    }

    // Hash password
    const saltRounds = 12;
    const hashedPassword = await bcrypt.hash(password, saltRounds);

    // Create user
    const user = await this.prisma.user.create({
      data: {
        email,
        phone,
        password: hashedPassword,
        gender: gender || Gender.UNKNOWN,
        nickname,
      },
      select: {
        id: true,
        email: true,
        phone: true,
        gender: true,
        nickname: true,
        avatar: true,
        createdAt: true,
        updatedAt: true,
      },
    });

    // Create user preferences
    await this.prisma.userPreference.create({
      data: {
        userId: user.id,
      },
    });

    // Generate JWT token
    const payload = { sub: user.id, email: user.email };
    const accessToken = this.jwtService.sign(payload);

    return {
      accessToken,
      tokenType: 'Bearer',
      expiresIn: 3600,
      user: {
        ...user,
        createdAt: formatBeijingISO(user.createdAt),
        updatedAt: formatBeijingISO(user.updatedAt),
      },
    };
  }

  // User login
  async login(loginDto: LoginDto): Promise<AuthResponseDto> {
    const { identifier, password } = loginDto;

    // Find user by email or phone number
    const user = await this.prisma.user.findFirst({
      where: {
        OR: [
          { email: identifier },
          { phone: identifier },
        ],
      },
    });

    if (!user) {
      throw new UnauthorizedException('User not found');
    }

    if (!user.isActive) {
      throw new UnauthorizedException('Account has been disabled');
    }

    // Validate password
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      throw new UnauthorizedException('Invalid password');
    }

    // Generate JWT token
    const payload = { sub: user.id, email: user.email };
    const accessToken = this.jwtService.sign(payload);

    // Return user information (excluding password)
    const userResponse: UserResponseDto = {
      id: user.id,
      email: user.email,
      phone: user.phone,
      gender: user.gender,
      nickname: user.nickname,
      avatar: user.avatar,
      createdAt: formatBeijingISO(user.createdAt),
      updatedAt: formatBeijingISO(user.updatedAt),
    };

    return {
      accessToken,
      tokenType: 'Bearer',
      expiresIn: 3600,
      user: userResponse,
    };
  }

  // Validate user
  async validateUser(userId: string): Promise<UserResponseDto | null> {
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      select: {
        id: true,
        email: true,
        phone: true,
        gender: true,
        nickname: true,
        avatar: true,
        createdAt: true,
        updatedAt: true,
      },
    });

    if (!user) return null;

    return {
      id: user.id,
      email: user.email,
      phone: user.phone,
      gender: user.gender,
      nickname: user.nickname,
      avatar: user.avatar,
      createdAt: formatBeijingISO(user.createdAt),
      updatedAt: formatBeijingISO(user.updatedAt),
    } as UserResponseDto;
  }

  // Update user information
  async updateUser(userId: string, updateUserDto: UpdateUserDto): Promise<UserResponseDto> {
    const { email, phone, password, confirmPassword, gender, nickname } = updateUserDto;

    // Validate password change
    if (password) {
      if (!confirmPassword) {
        throw new BadRequestException('Password confirmation required for password change');
      }
      if (password !== confirmPassword) {
        throw new BadRequestException('Passwords do not match');
      }
    }

    // Check if email is already used by another user
    if (email) {
      const existingUserByEmail = await this.prisma.user.findFirst({
        where: { 
          email,
          NOT: { id: userId }
        },
      });
      if (existingUserByEmail) {
        throw new ConflictException('Email already used by another user');
      }
    }

    // Check if phone number is already used by another user
    if (phone) {
      const existingUserByPhone = await this.prisma.user.findFirst({
        where: { 
          phone,
          NOT: { id: userId }
        },
      });
      if (existingUserByPhone) {
        throw new ConflictException('Phone number already used by another user');
      }
    }

    // Prepare update data
    const updateData: any = {};
    
    if (email !== undefined) updateData.email = email;
    if (phone !== undefined) updateData.phone = phone;
    if (gender !== undefined) updateData.gender = gender;
    if (nickname !== undefined) updateData.nickname = nickname;
    
    // If changing password, hash it
    if (password) {
      const saltRounds = 12;
      updateData.password = await bcrypt.hash(password, saltRounds);
    }

    // Update user information
    const updatedUser = await this.prisma.user.update({
      where: { id: userId },
      data: updateData,
      select: {
        id: true,
        email: true,
        phone: true,
        gender: true,
        nickname: true,
        avatar: true,
        createdAt: true,
        updatedAt: true,
      },
    });

    return {
      id: updatedUser.id,
      email: updatedUser.email,
      phone: updatedUser.phone,
      gender: updatedUser.gender,
      nickname: updatedUser.nickname,
      avatar: updatedUser.avatar,
      createdAt: formatBeijingISO(updatedUser.createdAt),
      updatedAt: formatBeijingISO(updatedUser.updatedAt),
    } as UserResponseDto;
  }
}